/*
 * Copyright (c) 2023-2023 elsfs Authors. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.elsfs.cloud.common.security.filter.applet;

import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Map;
import lombok.Setter;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.Assert;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;

/**
 * 小程序认证过滤器
 *
 * @author zeng
 */
public class AppletLoginAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
  public static final RequestMatcher requestMatcher =
      new AppletLoginRequestMatcher("/login/oauth2/code/*");
  private static final String CLIENT_REGISTRATION_NOT_FOUND_ERROR_CODE =
      "client_registration_not_found";
  @Setter private ClientRegistrationRepository clientRegistrationRepository;

  public AppletLoginAuthenticationFilter() {
    super(requestMatcher);
  }

  @Override
  public Authentication attemptAuthentication(
      HttpServletRequest request, HttpServletResponse response)
      throws AuthenticationException, IOException, ServletException {
    MultiValueMap<String, String> params = toMultiMap(request.getParameterMap());
    String appid = params.getFirst(AppletParameterNames.APPID);
    ClientRegistration clientRegistration =
        clientRegistrationRepository.findByRegistrationId(appid);
    if (clientRegistration == null) {
      OAuth2Error oauth2Error =
          new OAuth2Error(
              CLIENT_REGISTRATION_NOT_FOUND_ERROR_CODE,
              "Client Registration not found with Id: " + appid,
              null);
      throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
    }
    AppletType type = AppletType.getAppletType(params.getFirst(AppletParameterNames.TYPE));
    var authorizationRequest =
        AppletAuthorizationRequest.getAppletAuthorizationRequest(clientRegistration, params, type);
    var authenticationToken =
        new AppletAuthorizationCodeAuthenticationToken(clientRegistration, authorizationRequest);
    return this.getAuthenticationManager().authenticate(authenticationToken);
  }

  private static MultiValueMap<String, String> toMultiMap(Map<String, String[]> map) {
    MultiValueMap<String, String> params = new LinkedMultiValueMap<>(map.size());
    map.forEach(
        (key, values) -> {
          for (String value : values) {
            params.add(key, value);
          }
        });
    return params;
  }

  /** 匹配 */
  public static class AppletLoginRequestMatcher implements RequestMatcher {
    private final AntPathRequestMatcher antPathRequestMatcher;

    public AppletLoginRequestMatcher(String processesUri) {
      Assert.hasText(processesUri, "uri not is text");
      this.antPathRequestMatcher = new AntPathRequestMatcher(processesUri);
    }

    public AppletLoginRequestMatcher(AntPathRequestMatcher antPathRequestMatcher) {
      this.antPathRequestMatcher = antPathRequestMatcher;
    }

    @Override
    public boolean matches(HttpServletRequest request) {
      if (!antPathRequestMatcher.matches(request)) {
        return false;
      }
      Map<String, String[]> map = request.getParameterMap();
      return map.containsKey(AppletParameterNames.AUTH_CODE)
          && map.containsKey(AppletParameterNames.APPID);
    }
  }
}
